<?php
/**
 * Generic model class
 *
 * @author André Silva
 */
class Model {
	/**
	 * Custom properties
	 * @var array
	 */
	private $_properties = array();

	public function __get($property) {
		$annotation = Annotation::forClass(get_class($this));
		$get = 'get'.ucfirst($property);
		$value = null;
		if (method_exists($this, $get)) {
			$value = $this->$get();
			if($value === null && array_key_exists($property,$annotation['relationships'])) {
				return $this->join($property);
			}
		} elseif (isset($this->_properties[$property])) {
			$value = $this->_properties[$property];
		}
		return $value;
	}

	public function __set($property, $value) {
		$annotation = Annotation::forClass(get_class($this));
		if(array_key_exists($property,$annotation['columns']) && $annotation['columns'][$property] !== null) {
			$set = 'set'.ucfirst($annotation['columns'][$property]);
			$this->$set($value);
		} elseif (array_key_exists($property,$annotation['relationships'])) {
			$set = 'set'.ucfirst($property);
			$this->$set($value);
			if ($value !== null) {
				$relation = $annotation['relationships'][$property];
				$joinColumn = $annotation['properties'][$property][$relation]['joinColumn'];
				if (array_key_exists($joinColumn, $annotation['joinColumns'])) {
					$this->$joinColumn = $value->getPKValue();
				}
			}
		} else {
			if (array_key_exists($property,$annotation['joinColumns'])) {
				foreach ($annotation['joinColumns'][$property] as $joinColumn) {
					$this->$joinColumn = null;
				}
			}
			$this->_properties[$property] = $value;
		}
	}

	/**
	 * Create a copy of the current object
	 * @return Model
	 */
	public function __clone() {
		$class = get_class($this);
		$object = new $class();
		$annotation = Annotation::forClass($class);

		foreach (array_keys($annotation['columns']) as $property) {
			$object->$property = $this->$property;
		}
		return $object;
	}

	/**
	 * Return the primary key value
	 * @return mixed
	 */
	public function getPKValue() {
		$annotation = Annotation::forClass(get_class($this));
		$primary = $annotation['annotation']['Entity']['primaryKey'];
		return $this->$primary;
	}

	/**
	 * Return the objects using the class annotations
	 * @param string $property
	 * @return mixed
	 */
	public function &join($property) {
		$annotation = Annotation::forClass(get_class($this));
		if(array_key_exists($property, $annotation['relationships'])) {
			$type = $annotation['relationships'][$property];
			$rule = $annotation['properties'][$property][$annotation['relationships'][$property]];
			$className = $annotation['properties'][$property]['var'];
			$joinColumn = $rule['joinColumn'];
			switch ($type) {
				case 'OneToOne':
				case 'ManyToOne':
					$criteria = new Criteria($className);
					$result = $criteria->where($rule['referencedColumn'].'='.$this->$joinColumn)->get();
					break;

				case 'OneToMany':
					$criteria = new Criteria($className);
					$result = $criteria->where($rule['referencedColumn'].'='.$this->$joinColumn)->find();
					break;

				case 'ManyToMany':
					$joinTable = $annotation['properties'][$property]['JoinTable'];
					$criteria = new Criteria($className);
					$result = $criteria
							->join($joinTable)
							->where($joinTable['name'].'.'.$rule['referencedColumn'].'='.$this->$joinColumn)
							->find();
					break;

				default:
					throw new Exception('Invalid relationship: ' . get_class($this).'::'.$property);
					break;
			}
			$this->$property = $result;
		} else {
			throw new Exception('Invalid relationship: ' . get_class($this).'::'.$property);
		}
		return $result;
	}

	/**
	 * Persist the current object in database
	 * @param string $columns
	 * @param boolean $forcePrimary
	 * @return boolean
	 */
	public function insert($columns = null, $forcePrimary = false) {
		$criteria = new Criteria(get_class($this));
		if ($columns !== null) {
			$criteria->select($columns);
		}

		$sql = Connection::getSchema()->generateInsert($criteria, $this, $forcePrimary);

		try {
			$cmd = Connection::getInstance()->prepare($sql);
			$result = $cmd->execute();
			if($result) {
				$pk = $criteria->getPrimaryKey();
				$this->$pk = Connection::getInstance()->lastInsertId();
			}
			return $result;
		}
		catch (PDOException $e) {
			throw new Exception('SQL Error : ['.$sql.']. '.$e->getMessage());
		}
	}

	/**
	 * Update the current data in database
	 * @param string $columns
	 * @return boolean
	 */
	public function update($columns = null) {
		$criteria = new Criteria(get_class($this));

		$criteria->where($criteria->getPrimaryKey().'='.$this->getPKValue());

		if ($columns !== null) {
			$criteria->select($columns);
		}

		$sql = Connection::getSchema()->generateUpdate($criteria, $this);

		try {
			$cmd = Connection::getInstance()->prepare($sql);
			return $cmd->execute();
		}
		catch (PDOException $e) {
			throw new Exception('SQL Error : ['.$sql.']. '.$e->getMessage());
		}
	}

	/**
	 * Delete the object from database
	 * @return boolean
	 */
	public function delete() {
		$criteria = new Criteria(get_class($this));

		$criteria->where($criteria->getPrimaryKey().'='.$this->getPKValue());

		$sql = Connection::getSchema()->generateDelete($criteria);

		try {
			$cmd = Connection::getInstance()->prepare($sql);
			return $cmd->execute();
		}
		catch (PDOException $e) {
			throw new Exception('SQL Error : [' . $sql.']. '.$e->getMessage());
		}
	}
}
?>
